'use strict';
const fs = require('fs')
const path = require('path')
const os = require('os')

const inquirer = require('inquirer')
const fsExtra = require('fs-extra')
const semver = require('semver')
const glob = require('glob')
const ejs = require('ejs')

const Command = require('@xykj-cli-dev/command')
const log = require('@xykj-cli-dev/log')
const Package = require('@xykj-cli-dev/package')
const {spinnerStart,sleep,execAsync} = require('@xykj-cli-dev/utils')

const getProjectTemplate = require('./getProjectTemplate')

const userHome = os.homedir()

const TEMPLATE_TYPE_NORMAL = 'normal'
const EMPLATE_TYPE_CUSTOM = 'custom'

const WHITE_COMMOND = ['npm','cnpm'] // 白名单

class InitCommand extends Command{
    init(){
        this.projectName = this._argv[0] || ''
        // 全局的option参数要用 this._cmd.parent.opts().targetPath 这样取
        // 因为在 exec/lib/index 中要将参数转为字符串，opts方法没法转，所以设置了一个opts属性，值是opts()返回的值
        this.force = !!this._cmd.opts.force
        log.verbose('force',this.force)
    }
    async exec() {
        try{
            //1.准备阶段
            const projectInfo = await this.prepare()
            if(projectInfo){
                this.projectInfo = projectInfo
               // 2. 下载模板
               await this.downloadTemplate()
               // 3. 安装模板 
               await this.installTemplate()
            }
            
        }catch(e){
            console.log('出错了',e)
        }
    }
    async installTemplate(){
        if(this.templateInfo){
            if(!this.templateInfo.type){
                // 有type则为自定义安装
                this.templateInfo.type = TEMPLATE_TYPE_NORMAL
            }
            console.log('type',this.templateInfo.type);
            if(this.templateInfo.type === TEMPLATE_TYPE_NORMAL){
                // 标准安装
                await this.installNormalTemplate()
            } else if(this.templateInfo.type === EMPLATE_TYPE_CUSTOM){
                // 自定义安装
                await this.installCustomTemplate()
            }else{
                throw new Error('无法识别项目模板类型')
            }
        }else{
            throw new Error('未获取到模板信息')
        }
    }
    async installNormalTemplate(){
        let spinner = spinnerStart('正在安装模板...')
        try{
            const templatePath = path.resolve(this.templateNpm.cacheFilePath)
            const targetPath = process.cwd()
            console.log('模板路径',templatePath);
            console.log('target',targetPath);
            fsExtra.ensureDirSync(templatePath)
            fsExtra.emptyDirSync(targetPath)
            fsExtra.copySync(templatePath,targetPath)
        }catch(e){
            throw e
        }finally{
            spinner.stop(true)
            console.log('安装成功')
        }
        // 需要忽略的文件
        const templateIgnore = this.templateInfo.ignore || []
        const ignore = ['node_modules/**',...templateIgnore]
        await this.ejsRender({ignore})
        // 安装依赖
        const {installCommand,startCommand} = this.templateInfo

        //await this.execCommad(installCommand,'安装依赖失败了')
        // 启动命令执行
        //await this.execCommad(startCommand,'启动服务失败了') 
    }
    // 执行安装命令
    async execCommad(installCommand,errMessage){
        let installRet
        if(installCommand && installCommand.length > 0){
            const installCmd = installCommand.split(' ')
            const cmd = this.checkCommand(installCmd[0])
            const args = installCmd.slice(1)
            // 返回结果为 0 表示安装依赖成功
            installRet = await execAsync(cmd,args,{
                stdio:"inherit",
                cwd:process.cwd()
            })
        }
        if(installRet !== 0){
            throw new Error(errMessage)
        }
    }
    // 检查指令是否在白名单中
    checkCommand(cmd){
        return WHITE_COMMOND.includes(cmd) ? cmd : null
    }
    // ejs 渲染模板
    ejsRender(options){
        const dir = process.cwd()
        return new Promise((resolve,reject)=>{
            glob('**',{
                cwd:dir,
                ignore:options.ignore,
                nodir:true, // 不要文件夹
            },(err,files)=>{
                Promise.all(files.map(file=>{
                    const filePath = path.join(dir,file)
                    return new Promise((resolve1,reject1)=>{
                        ejs.renderFile(filePath,this.projectInfo,{},(err,result)=>{
                            if(err){
                                reject1(err)
                            }else{
                                fsExtra.writeFileSync(filePath,result)
                                resolve1(result)
                            }
                        })
                    })
                })).then(()=>{
                    resolve()
                }).catch(err=>{
                    reject(err)
                })
            })
        })
    }
    async installCustomTemplate(){
        // 查询自定义模板的入口文件
        if(await this.templateNpm.exists()){
            const rootFile = this.templateNpm.getRootFilePath()
            if(fs.existsSync(rootFile)){
                console.log('开始执行自定义模板')
                const templatePath = path.resolve(this.templateNpm.cacheFilePath,'template')
                const options = {
                    ...this.templateInfo,
                    ...this.projectInfo,
                    sourcePath:templatePath,
                    targetPath:process.cwd()
                }
                const code = `require('${rootFile}')(${JSON.stringify(options)})`
                log.verbose('code',code)
                await execAsync('node',['-e',code],{stdio:'inherit',cwd:process.cwd()})
                log.verbose('自定义模板安装成功')
            }else{
                console.log('未定义自定义安装文件');
            }
        }
        
    }
    async downloadTemplate(){
        const {projectTemplate} = this.projectInfo
        this.templateInfo = this.template.find(item=>item.npmName === projectTemplate)
        const targetPath = path.resolve(userHome,'.xykj','template')
        const storePath = path.resolve(userHome,'.xykj','template','node_modules')
        const {npmName,version} = this.templateInfo
        const templateNpm = new Package({
            targetPath,
            storePath,
            packageName:npmName,
            packageVersion:version
        })
        this.templateNpm = templateNpm
        if(!await templateNpm.exists()){
            const spinner = spinnerStart('正在下载模板...')
            await sleep(3000)
            try {
                await templateNpm.install()
            } catch (error) {
                throw error
            }finally{
                spinner.stop(true)
                if(await templateNpm.exists()){
                    log.success('下载模板成功')
                }
            }
        }else{
            const spinner = spinnerStart('正在更新模板...')
            await sleep(3000)
            try {
                await templateNpm.update()
            } catch (error) {
                throw error
            } finally {
                spinner.stop(true)
                if(await templateNpm.exists()){
                    log.success('更新模板成功')
                }
            }      
        }
        console.log('userHome',userHome);
    }
    async prepare(){
        // 判断项目模板是否存在
        const template = await getProjectTemplate()
        console.log('模板信息',template);
        if(!template || template.length == 0){
            throw new Error('项目模板不存在')
        }
        this.template = template
        // 获取当前执行命令的目录  path.resolve('.') 也可以获取到
        const localPath = process.cwd()
        // 1 判断当前目录是否为空
        if(!this.isDirEmpty(localPath)){
            let ifContinue = false
            if(!this.force){
                const answer = await inquirer.prompt({
                    type:'confirm',
                    name:'ifContinue',
                    message:'当前文件夹不为空，是否继续创建项目',
                })
                ifContinue = answer.ifContinue
                if(!ifContinue){
                    return 
                }
            }
            if(ifContinue || this.force){
                // 二次确认
                const {confirmDelete} = await inquirer.prompt({
                    type:'confirm',
                    name:'confirmDelete',
                    default:false,
                    message:'是否确认清空当前目录下的文件?'
                })
                if(confirmDelete){
                    //清空当前目录
                    fsExtra.emptyDirSync(localPath)
                }
            }
        }
        return await this.getProjectInfo()
    }
    async getProjectInfo(){
        // 判断项目名是否合法
        function projectNameIsValid(v){
            return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v)
        }
        let projectInfo = {}
        // 1 选择创建项目或组件
        const {type} = await inquirer.prompt({
            type:'list',
            message:'创建项目还是创建组件',
            name:'type',
            default:'project',
            choices:[{
                name:'项目',
                value:'project'
            },{
                name:'组件',
                value:'component'
            }]
        })
        log.verbose('创建项目还是组件',type)
        this.template = this.template.filter(template=>{
            return template.tag.includes(type)
        })
        let title = type === 'project' ? '项目' : '组件'
        console.log('filter',this.template);
        let projectPromptArr = []
        if(!projectNameIsValid(this.projectName)){
            projectPromptArr.push({
                    type:'input',
                    message:`请输入${title}名称`,
                    name:'projectName',
                    default:'',
                    validate:function(v){
                        // const done = this.async()
                        // setTimeout(function(){
                        //     if(!/^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v)){
                        //         done('请输入合法的项目名称')
                        //         return
                        //     }
                        //     console.log('项目名称合法了');
                        //     done(null,true)
                        // },0)
                        //首字符时字符，尾字符可以是字符或者数字
                        //符号仅允许使用 - _
                        return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v)
                    }
                })
        }
        projectPromptArr.push(
            {
                type:'input',
                name:'projectVersion',
                message:`请输入${title}版本号`,
                default:'1.0.0',
                validate:function(v){
                    return !!semver.valid(v)
                }
            },{
                type:'list',
                name:'projectTemplate',
                message:`请选择${title}模板`,
                choices:this.createTemplateChoice()
            }
        )
        if(type === 'project'){
            // 2 获取项目的基本信息
            const o = await inquirer.prompt(projectPromptArr)
            projectInfo = {
                projectName:this.projectName,
                type,
                ...o
            }
        }else if(type === 'component'){
            const desc = {
                type:'input',
                name:'describe',
                message:`${title}描述信息`,
                validate:function(v){
                    return v !== ''
                }
            }
            projectPromptArr.push(desc)
            const o = await inquirer.prompt(projectPromptArr)
            projectInfo = {
                projectName:this.projectName,
                type,
                ...o
            }
        }

        // 生成className
        if(projectInfo.projectName){
            projectInfo.className = require('kebab-case')(projectInfo.projectName).replace(/^-/,'')
            projectInfo.version = projectInfo.projectVersion
        }

        return projectInfo
    }
    createTemplateChoice(){
        return this.template.map(item=>{
            return {
               value: item.npmName,
               name: item.name 
            }
        })
    }
    isDirEmpty(localPath){
        let fileList = fs.readdirSync(localPath)
        // 排除掉不需要统计的文件或文件夹
        fileList = fileList.filter(file=>(
            !file.startsWith('.') && ['node_modules'].indexOf(file) < 0
        ))
        return  fileList.length <= 0
    }
}
// action 执行的时候
function init(argv) {
   // console.log('init',projectName,cmdObj,a,b.parent.opts())
   // console.log('init',args,other,optis.parent.opts())
    return new InitCommand(argv)
}
module.exports = init
module.exports.InitCommand = InitCommand
